package com.xuecheng.media.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.xuecheng.base.exception.XueChengPlusException;
import com.xuecheng.base.model.PageParams;
import com.xuecheng.base.model.PageResult;
import com.xuecheng.base.model.RestResponse;
import com.xuecheng.media.mapper.MediaFilesMapper;
import com.xuecheng.media.mapper.MediaProcessMapper;
import com.xuecheng.media.model.dto.QueryMediaParamsDto;
import com.xuecheng.media.model.dto.UploadFileParamsDto;
import com.xuecheng.media.model.dto.UploadFileResultDto;
import com.xuecheng.media.model.po.MediaFiles;
import com.xuecheng.media.model.po.MediaProcess;
import com.xuecheng.media.service.MediaFileService;
import io.minio.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

/**
 * @description TODO
 * @author Mr.M
 * @date 2022/9/10 8:58
 * @version 1.0
 */
@Slf4j
 @Service
public class MediaFileServiceImpl implements MediaFileService {

  @Autowired
 MediaFilesMapper mediaFilesMapper;

  @Autowired
 MediaProcessMapper mediaProcessMapper;

  @Autowired
 MinioClient minioClient;

  @Autowired
  MediaFileService currentProxy;

  //普通文件存储的桶
 @Value("${minio.bucket.files}")
 private String bucket_files;

 //视频文件存储的桶
 @Value("${minio.bucket.videofiles}")
 private String bucket_videofiles;

 @Override
 public PageResult<MediaFiles> queryMediaFiels(Long companyId,PageParams pageParams, QueryMediaParamsDto queryMediaParamsDto) {

  //构建查询条件对象
  LambdaQueryWrapper<MediaFiles> queryWrapper = new LambdaQueryWrapper<>();
  queryWrapper.like(StringUtils.isNotEmpty(queryMediaParamsDto.getFilename()),MediaFiles::getFilename,queryMediaParamsDto.getFilename());
  queryWrapper.eq(StringUtils.isNotEmpty(queryMediaParamsDto.getFileType()),MediaFiles::getFileType,queryMediaParamsDto.getFileType());
  
  //分页对象
  Page<MediaFiles> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
  // 查询数据内容获得结果
  Page<MediaFiles> pageResult = mediaFilesMapper.selectPage(page, queryWrapper);
  // 获取数据列表
  List<MediaFiles> list = pageResult.getRecords();
  // 获取数据总数
  long total = pageResult.getTotal();
  // 构建结果集
  PageResult<MediaFiles> mediaListResult = new PageResult<>(list, total, pageParams.getPageNo(), pageParams.getPageSize());
  return mediaListResult;

 }

//  @Transactional
  @Override
  public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, byte[] bytes, String folder, String objectName) {

  //得到文件的md5值
   String fileMd5 = DigestUtils.md5Hex(bytes);
   String filename = uploadFileParamsDto.getFilename();

   if (StringUtils.isEmpty(folder)){
   //自动生成目录的路径，按年月日生成
   folder = getFileFolder(new Date(), true, true, true);
  }else if(folder.indexOf("/")<0){
   folder = folder + "/";
  }

   if(StringUtils.isEmpty(objectName)){
    //如果objectName为空，使用文件的md5值为objectName
   objectName = fileMd5 + filename.substring(filename.lastIndexOf("."));
  }
   //对象名称
  objectName = folder + objectName;
  try {
   //上传至文件系统
//   ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
//
//   String contentType = uploadFileParamsDto.getContentType();
//
//   PutObjectArgs putObjectArgs = PutObjectArgs.builder()
//           .bucket(bucket_files)
//           .object(objectName)
//           //InputStream stream, long objectSize 对象大小, long partSize 分片大小(-1表示5M,最大不超过5T，10000)
//           .stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
//           .contentType(contentType)
//           .build();
//
//   //上传到minio
//   minioClient.putObject(putObjectArgs);
   addMediaFilesToMinIO(bytes,bucket_files,objectName,uploadFileParamsDto.getContentType());


   //上传到数据库
//   MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
//   if(mediaFiles == null){
//    mediaFiles = new MediaFiles();
//    //封装数据
//    BeanUtils.copyProperties(uploadFileParamsDto,mediaFiles);
//    mediaFiles.setId(fileMd5);
//    mediaFiles.setFileId(fileMd5);
//    mediaFiles.setCompanyId(companyId);
//    mediaFiles.setFilename(filename);
//    mediaFiles.setBucket(bucket_files);
//    mediaFiles.setFilePath(objectName);
//    mediaFiles.setUrl("/"+bucket_files+"/"+objectName);
//    mediaFiles.setCreateDate(LocalDateTime.now());
//    mediaFiles.setStatus("1");
//    mediaFiles.setAuditStatus("002003");
    //插入文件表
   MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId,fileMd5,uploadFileParamsDto,bucket_files,objectName);
//    int insert = mediaFilesMapper.insert(mediaFiles);
//    if (insert < 0){
//     XueChengPlusException.cast("上传文件过程失败");
//    }

 //准备返回数据
     UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
     BeanUtils.copyProperties(mediaFiles,uploadFileResultDto);
     return uploadFileResultDto;
//   }
  }catch (Exception e){
   e.printStackTrace();
   log.debug("上传文件失败：{}",e.getMessage());
   throw new RuntimeException(e.getMessage());

  }
//   return null;
  }

 //根据日期拼接目录
 private String getFileFolder(Date date, boolean year, boolean month, boolean day) {
  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  //获取当前日期字符串
  String dateString = sdf.format(new Date());
  //取出年、月、日
  String[] dateStringArray = dateString.split("-");
  StringBuffer folderString = new StringBuffer();
  if (year) {
   folderString.append(dateStringArray[0]);
   folderString.append("/");
  }
  if (month) {
   folderString.append(dateStringArray[1]);
   folderString.append("/");
  }
  if (day) {
   folderString.append(dateStringArray[2]);
   folderString.append("/");
  }
  return folderString.toString();
 }

 //将大文件上传到文件系统
 public void addMediaFilesToMinIO(String filePath, String bucket, String objectName){
  try {
   UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
           .bucket(bucket)
           .object(objectName)
           .filename(filePath)
           .build();
   //上传
   minioClient.uploadObject(uploadObjectArgs);
   log.debug("合并后的文件上传成功:{}",filePath);
   System.out.println("合并后的上传成功");
  } catch (Exception e) {
   XueChengPlusException.cast("合并后的文件上传到文件系统失败");
  }
 }
 /**
  * @description 将文件写入minIO
  * @param bytes  文件字节数组
  * @param bucket  桶
  * @param objectName 对象名称
  * @param contentType  内容类型
  * @return void
  * @author Mr.M
  * @date 2022/10/12 21:22
  */
 public void addMediaFilesToMinIO(byte[] bytes, String bucket, String objectName, String contentType) {
  //转为流
  ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);

  try {
   PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(bucket).object(objectName)
           //-1表示文件分片按5M(不小于5M,不大于5T),分片数量最大10000，
           .stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
           .contentType(contentType)
           .build();

   minioClient.putObject(putObjectArgs);
  } catch (Exception e) {
   e.printStackTrace();
   XueChengPlusException.cast("上传文件到文件系统出错");
  }
 }

 /**
  * @description 将文件信息添加到文件表
  * @param companyId  机构id
  * @param fileMd5  文件md5值
  * @param uploadFileParamsDto  上传文件的信息
  * @param bucket  桶
  * @param objectName 对象名称
  * @return com.xuecheng.media.model.po.MediaFiles
  * @author Mr.M
  * @date 2022/10/12 21:22
  */
 @Transactional
 public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName) {

  //从数据库查询文件
  MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
  if (mediaFiles == null) {
   mediaFiles = new MediaFiles();
   //拷贝基本信息
   BeanUtils.copyProperties(uploadFileParamsDto, mediaFiles);
   mediaFiles.setId(fileMd5);
   mediaFiles.setFileId(fileMd5);
   mediaFiles.setCompanyId(companyId);
   mediaFiles.setFilePath(objectName);

   //获取扩展名
   String filename = uploadFileParamsDto.getFilename();
   String extName = null;
   if (StringUtils.isNotEmpty(filename) && filename.indexOf(".")>0){
      extName = filename.substring(filename.lastIndexOf("."));
   }
   //媒体类型
   String mediaType = getMediaTypeByextName(extName);
//图片、mp4视频设置URL
   if (mediaType.indexOf("image")>=0 || mediaType.indexOf("mp4")>=0){
    mediaFiles.setUrl("/" + bucket + "/" + objectName);
   }

   mediaFiles.setBucket(bucket);
   mediaFiles.setCreateDate(LocalDateTime.now());
   mediaFiles.setAuditStatus("002003");
   mediaFiles.setStatus("1");
   //保存文件信息到文件表
   int insert = mediaFilesMapper.insert(mediaFiles);
//   int a=1/0;
   if (insert < 0) {
    XueChengPlusException.cast("保存文件信息失败");
   }

   //将avi视频添加到待处理任务表
   if (mediaType.equals("video/x-msvideo")){
    MediaProcess mediaProcess = new MediaProcess();
    BeanUtils.copyProperties(mediaFiles,mediaProcess);
    mediaProcessMapper.insert(mediaProcess);
    //更新URL为空
    mediaFiles.setUrl(null);
    mediaFilesMapper.updateById(mediaFiles);


   }

  }
  return mediaFiles;
 }

 @Override
 public RestResponse<Boolean> checkFile(String fileMd5) {
  //查询文件信息  在文件表存在，并且在文件系统存在，此文件才存在
  MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
  if(mediaFiles ==null){
   //文件不存在
   return RestResponse.success(false);
  }
  //查看文件是否在文件系统中
   //桶
   String bucket = mediaFiles.getBucket();
   //存储目录
   String filePath = mediaFiles.getFilePath();
   //文件流
   InputStream stream = null;
   GetObjectArgs getObjectArgs = GetObjectArgs.builder()
           .bucket(bucket)
           .object(filePath)
           .build();
   try {
    stream = minioClient.getObject(getObjectArgs);
    if (stream ==null ){
     //文件不存在
     return RestResponse.success(false);
    }
   }catch (Exception e){
    //文件不存在
    return RestResponse.success(false);
   }

  //文件已存在
  return RestResponse.success(true);
 }

 @Override
 public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex) {
  //得到分块文件的目录
  String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
  //得到分块文件的路径
  String chunkFilePath = chunkFileFolderPath + chunkIndex;
  //文件流
  InputStream FileStream = null;
  GetObjectArgs getObjectArgs = GetObjectArgs.builder()
          .bucket(bucket_videofiles)
          .object(chunkFilePath)
          .build();
  try {
   FileStream = minioClient.getObject(getObjectArgs);
   if (FileStream ==null){
      //分块文件不存在
    return RestResponse.success(false);
   }
  }catch (Exception e){
//分块文件不存在
   return RestResponse.success(false);
  }
  //分块文件已存在
  return RestResponse.success(true);
 }

 //得到分块文件的目录
 private String getChunkFileFolderPath(String fileMd5){
  return fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/" + "chunk" + "/";
 }

 @Override
 public RestResponse uploadChunk(String fileMd5, int chunk, byte[] bytes) {
  //得到分块文件的目录路径
  String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
  //得到分块文件的路径
  String chunkFilePath = chunkFileFolderPath + chunk;
  try {
   //将文件存进MinIO
   addMediaFilesToMinIO(bytes,bucket_videofiles,chunkFilePath,"application/octet-stream");
  }catch (Exception e){
   e.printStackTrace();
   XueChengPlusException.cast("上传过程中出错，请重试");
  }
  return RestResponse.success();
 }

 //检查所有分块是否上传完毕 ->下载分块
 private File[] checkChunkStatus(String fileMd5, int chunkTotal) {
  //得到分块文件的目录
  String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
  //分块文件数组
  File[] files = new File[chunkTotal];
  //检查分块是否上传完毕
  for (int i = 0; i < chunkTotal; i++) {
   //分块文件的路径
   String chunkFilePath = chunkFileFolderPath + i;
   //下载文件
   File chunkFile = null;
   try {
    chunkFile = File.createTempFile("chunk" + i, null);
   }catch (IOException e){
    e.printStackTrace();
    XueChengPlusException.cast("下载分块时创建临时文件出错");
   }
   downloadFileFromMinIO(chunkFile,bucket_videofiles,chunkFilePath);
   //将分块文件加入数组
   files[i]=chunkFile;
  }
  return files;
 }

 //根据桶和文件路径从minio下载文件
 public File downloadFileFromMinIO(File file,String bucket,String objectName){
  InputStream fileInputStream = null;
  OutputStream fileOutputStream = null;
  GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(bucket).object(objectName).build();
  try {
  fileInputStream = minioClient.getObject(getObjectArgs);
     try {
      fileOutputStream = new FileOutputStream(file);
      IOUtils.copy(fileInputStream,fileOutputStream);
     }catch (IOException e){
      XueChengPlusException.cast("下载文件"+objectName+"出错");
     }
  }catch (Exception e){
   e.printStackTrace();
   XueChengPlusException.cast("文件不存在"+objectName);
  } finally {

   if(fileInputStream != null){
    try {
     fileInputStream.close();
    }catch (IOException e){
     e.printStackTrace();
    }
   }

   if(fileOutputStream != null){
    try {
     fileOutputStream.close();
    }catch (IOException e){
     e.printStackTrace();
    }
   }

  }
  return file;
 }

 @Override
 public RestResponse mergechunks(Long companyId, String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto) {
  String filename = uploadFileParamsDto.getFilename();
  //下载所有分块的文件
  File[] chunkFiles = checkChunkStatus(fileMd5, chunkTotal);

  //得到合并文件的扩展名
  String extName = filename.substring(filename.lastIndexOf("."));

  //创建一个临时文件作为合并文件
  File tempMergeFile = null;
  try {
   try {
    tempMergeFile = File.createTempFile("merge", extName);
   } catch (IOException e) {
    XueChengPlusException.cast("创建临时合并文件出错");
   }


   //创建合并文件的流对象
   try ( RandomAccessFile raf_write = new RandomAccessFile(tempMergeFile, "rw");){
    byte[] b = new byte[1024];
    for (File file : chunkFiles) {
     //读取分块文件的流对象
     try (RandomAccessFile raf_read = new RandomAccessFile(file, "r");){
      int len = -1;
      while ((len = raf_read.read(b)) != -1) {
       //向合并文件写数据
       raf_write.write(b, 0, len);
      }
     }
    }
   } catch (IOException e) {
    e.printStackTrace();
    XueChengPlusException.cast("合并文件过程出错");
   }

   //检验合并后的文件是否正确
   try ( FileInputStream mergeFileStream = new FileInputStream(tempMergeFile);){
    String mergemd5Hex = DigestUtils.md5Hex(mergeFileStream);
    if (!fileMd5.equalsIgnoreCase(mergemd5Hex)) {
     log.debug("合并文件校验不通过,文件路径:{},原始文件md5值:{}", tempMergeFile.getAbsolutePath(), fileMd5);
     XueChengPlusException.cast("合并文件校验不通过");
    }
   } catch (IOException e) {
    e.printStackTrace();
    log.debug("合并文件校验出错,文件路径:{},原始文件md5值:{}", tempMergeFile.getAbsolutePath(), fileMd5);
    XueChengPlusException.cast("合并文件校验异常");
   }

   //将合并后的文件上传到文件系统
   //拿到合并文件在MInio的存储路径
   String mergeFilePath = getFilePathByMd5(fileMd5, extName);
   try {
    //上传文件到MinIO
    addMediaFilesToMinIO(tempMergeFile.getAbsolutePath(), bucket_videofiles, mergeFilePath);
    log.debug("合并文件上传MinIO完成{}",tempMergeFile.getAbsolutePath());
   } catch (Exception e) {
    e.printStackTrace();
    XueChengPlusException.cast("合并文件时上传文件出错");
   }

   //将合并后的文件保存到数据库
   uploadFileParamsDto.setFileSize(tempMergeFile.length());  //合并文件的大小
   MediaFiles mediaFiles = addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_videofiles, mergeFilePath);
   if (mediaFiles == null){
    XueChengPlusException.cast("媒资文件入库出错");
   }

   return RestResponse.success(true);
  }finally {
   //删除临时分块文件
   if (chunkFiles!=null){
    for (File chunkFile : chunkFiles) {
     if (chunkFile.exists()){
      chunkFile.delete();
     }
    }
   }

   //删除临时的合并文件
   if (tempMergeFile!=null){
    tempMergeFile.delete();
   }

  }
 }

 @Override
 public MediaFiles getFileById(String mediaId){
  MediaFiles mediaFiles = mediaFilesMapper.selectById(mediaId);
 if (mediaFiles == null){
  XueChengPlusException.cast("文件不存在");
 }
  String mediaFilesUrl = mediaFiles.getUrl();
 if (StringUtils.isEmpty(mediaFilesUrl)){
  XueChengPlusException.cast("文件还没有处理，请稍后再试");
 }
  return mediaFiles;
 }


 private String getFilePathByMd5(String fileMd5,String fileExt){
  return   fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/" +fileMd5 +fileExt;
 }

 private String getBigFilePathByMd5(String fileMd5){
  return   fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 ;
 }

 //根据扩展名拿匹配的媒体类型
 private String getMediaTypeByextName(String extName){
   //资源的媒体类型
  String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE; //默认未知二进制流
  if (StringUtils.isNotEmpty(extName)){
   ContentInfo extNameMatch = ContentInfoUtil.findExtensionMatch(extName);
   if (extNameMatch != null){
    contentType = extNameMatch.getMimeType();
   }
  }
  return contentType;
 }

 @Override
 public void deleteFile(String mediaId) {
  MediaFiles mediaFiles = mediaFilesMapper.selectById(mediaId);
  String filename = mediaFiles.getFilename();
  deleteFile(minioClient,mediaId,filename);
  mediaFilesMapper.deleteById(mediaId);
 }


 //从MInIO删除文件
 private boolean deleteFile(MinioClient minioClient, String fileMd5, String fileName) {
  try {
   String extName = fileName.substring(fileName.lastIndexOf("."));
   //普通文件的路径
   String path = getFileFolder(new Date(), true, true, true);
   String filePath = path + fileMd5 + extName ;
   //删除普通文件的信息参数
   RemoveObjectArgs fileArgs = RemoveObjectArgs.builder().bucket(bucket_files).object(filePath).build();

   //删除普通文件
   minioClient.removeObject(fileArgs);

   //大文件的路径
   String bigFilePath = getBigFilePathByMd5(fileMd5) + "/" + fileMd5 +extName;
//删除大文件的信息参数
   RemoveObjectArgs bigFileArgs = RemoveObjectArgs.builder().bucket(bucket_videofiles).object(bigFilePath).build();

   //删除大文件
   minioClient.removeObject(bigFileArgs);

   //分块大小
   MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
   int chunkSize = 1024 * 1024 * 1; //1M
   long chunkNum = (long) Math.ceil(mediaFiles.getFileSize() * 1.0 / chunkSize);

   for (long i = 0; i < chunkNum; i++) {
    //分块文件的路径
    String chunkPath = getChunkFileFolderPath(fileMd5) + i;
    //删除分块文件的信息参数
    RemoveObjectArgs chunkFileArgs = RemoveObjectArgs.builder().bucket(bucket_videofiles).object(chunkPath).build();

    //删除分块文件
    minioClient.removeObject(chunkFileArgs);
   }
  } catch (Exception e) {
   e.printStackTrace();
   XueChengPlusException.cast("文件删除失败,"+e.getMessage());
   return false;
  }
  return true;
 }




}
